﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;

using System.Threading;
using System.Collections;
using YouTubeAccessLayer;
using DatabaseLayer;
using FileFolderOperations;
using FacebookAccessLayer;

namespace IVRJunctionModerationService
{
    public partial class IVRJunctionModeration : ServiceBase
    {
        private Thread threadServiceLogic;

        private FileFunctions objectFileFunctions;

        private String sSource = "IVR Junction- Moderation Service";
        private String sLog = "Application";
          
        // configuration data
        private String dropBoxFolderLocation;
        private String developerKey;
        private String accountName;
        private String username;
        private String password;

        private String branchId;

        private bool isModerationOnline;
        private bool isSynchronizeBranch;
        private bool isConfigureFacebook;

        private String facebookAccessToken;


        public IVRJunctionModeration()
        {

            InitializeComponent();
            objectFileFunctions = new FileFunctions();
            this.ServiceName = "IVR Junction- Moderation Service";
            
            if (!EventLog.SourceExists(sSource))
                EventLog.CreateEventSource(sSource, sLog);
            EventLog.WriteEntry(sSource, "Constructor Started");

            DatabaseFunctions objectDB = new DatabaseFunctions();
            var configurationData = objectDB.getIVRJunctionConfiguration();
            // if the configuration data is set and readable then execute the service code
            if (configurationData != null && configurationData.Item1 != null)
            {
                branchId = configurationData.Item1;

                var youTubeConfigurationInformation = configurationData.Item3;
                developerKey = youTubeConfigurationInformation.Item1;
                accountName = youTubeConfigurationInformation.Item2;
                username = youTubeConfigurationInformation.Item3;
                password = youTubeConfigurationInformation.Item4;

                dropBoxFolderLocation = configurationData.Item5+"\\";
                // appending '//'  as a file name would be added to this folder later

                var genericConfigurationInformationTuple = configurationData.Item6;
                isModerationOnline = genericConfigurationInformationTuple.Item1;
                isSynchronizeBranch = genericConfigurationInformationTuple.Item2;
                isConfigureFacebook = genericConfigurationInformationTuple.Item3;

                facebookAccessToken = configurationData.Item7;

                EventLog.WriteEntry(sSource, "Service started successfully", EventLogEntryType.Information);
            }
            else
            {
                EventLog.WriteEntry(sSource, "Service is communicated to stop as Configuration data is not set", EventLogEntryType.Error);
                this.Stop();
            }
        }

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);
            try
            {
                threadServiceLogic = new Thread(ServiceLogic);
                threadServiceLogic.Start();
                EventLog.WriteEntry(sSource, "Service Started");
            }
            catch (ArgumentNullException exceptionArgumentNullException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, exceptionArgumentNullException.Message,EventLogEntryType.Error);
            }
            catch (ThreadStateException exceptionThreadStateException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, exceptionThreadStateException.Message, EventLogEntryType.Error);

            }
            catch (OutOfMemoryException exceptionOutOfMemoryException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, exceptionOutOfMemoryException.Message, EventLogEntryType.Error);

            }

        }

        protected override void OnStop()
        {
            base.OnStop();

            while (threadServiceLogic.ThreadState == System.Threading.ThreadState.Running)
            {
                // waiting infinitely for consumer thread to finish the processing.
            }

            // terminate the consumer thread
            try
            {
                threadServiceLogic.Abort();
                EventLog.WriteEntry(sSource, "Service Stopped!");

            }
            catch (System.Security.SecurityException exceptionSecurityException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, exceptionSecurityException.Message, EventLogEntryType.Error);

            }
            catch (ThreadStateException exceptionThreadStateException)
            {
                // log this event in the event logger
                EventLog.WriteEntry(sSource, exceptionThreadStateException.Message, EventLogEntryType.Error);

            }
        }

        public void ServiceLogic()
        {
            
            YouTubeFunctions objectYouTubeFunctions = new YouTubeFunctions(developerKey, accountName, username, password);
            bool exceptionOccured = false;
            
           // If the service is installed and started, it will work if and only if the moderation is allowed online
            while (isModerationOnline)
            {
                if (exceptionOccured)
                {
                    exceptionOccured = false;
                    Thread.Sleep(60000);
                    //Console.WriteLine(Thread.CurrentThread.ThreadState);
                }

                Hashtable youTubeVideoRecord = objectYouTubeFunctions.getVideoIdModerationStatusVideoStatusFBStatus(username, branchId);

                // if videoRecord is null implies an error occured while fetching the video details from the YouTube.
                if (youTubeVideoRecord == null)
                {
                    exceptionOccured = true;
                    EventLog.WriteEntry(sSource, "Error fetching video info from YouTube", EventLogEntryType.Error);
                    continue;

                }

                // read the AudioFileId, YouTube Video Id and Moderation Status from the Audio database

                DatabaseFunctions objectDatabaseFunctions = new DatabaseFunctions();
                List<Tuple<String, String, Boolean>> audioDatabaseInformation =
                    objectDatabaseFunctions.getAudioIdVideoIdAndModerationStatus();

                if (audioDatabaseInformation == null)
                {
                    // error occured in reading the database
                    // go to sleep and then start processing all the information again
                    exceptionOccured = true;
                    //Log this in the event logger
                    EventLog.WriteEntry(sSource, "Error Fetching data from Audio Database", EventLogEntryType.Error);
                    continue;
                }


                EventLog.WriteEntry(sSource, "Data successfully fetched from YouTube and AudioDB");

                // below statements are just for the purpose of debugging
                if(audioDatabaseInformation.Count != youTubeVideoRecord.Count)
                    EventLog.WriteEntry(sSource, "Files in audio DB corresponding to a branch is not equal to video files on YT. Audio record vs. Video Record:" +
                            audioDatabaseInformation.Count + " vs. "+ youTubeVideoRecord.Count, EventLogEntryType.Error);

                EventLog.WriteEntry(sSource, "Number of video files on YT : " + youTubeVideoRecord.Count, EventLogEntryType.Information);

               

                int count = 0;
                //IDictionaryEnumerator videoRecordEnumerator = videoRecord.GetEnumerator();

                while (count < audioDatabaseInformation.Count)
                {
                    // reading first row in audioDBInformationList 
                    EventLog.WriteEntry(sSource, "Processing audio record", EventLogEntryType.Information);

                    Tuple<String, String, Boolean> rowInList = audioDatabaseInformation[count];

                    String audioFileLocationinAudioDB = rowInList.Item1;
                    String youTubeVideoIDinAudioDB = rowInList.Item2;
                    Boolean isPrivateInAudioDB = rowInList.Item3;

                    // clean up code

                    // check whether a video corresponding to the audio is deleted or present
                    bool videoPresentOnYouTube = youTubeVideoRecord.ContainsKey(youTubeVideoIDinAudioDB);

                    // if video is deleted on YouTube implies delete the corresponding file from audio DB
                    if (videoPresentOnYouTube == false)
                    {

                        EventLog.WriteEntry(sSource, "Video corresponding to a record in Audio DB is absent." + "Details are: YT Video Id="
                            +youTubeVideoIDinAudioDB + " and audioId=" + audioFileLocationinAudioDB, EventLogEntryType.Error);


                        // Check whether the audio was uploaded on cloud storage
                        // if yes then delete it from skydrive cloud Storage
                        int cloudStorageUploadStatus =
                            objectDatabaseFunctions.getCloudStorageStatusOfAudioFile(audioFileLocationinAudioDB);

                        // If the read attempt result in SQL Error
                        if (cloudStorageUploadStatus == -1)
                        {
                            // sql error, log this in the event logger
                            EventLog.WriteEntry(sSource, "SQL error in reading cloud storage of audio file", EventLogEntryType.Error);

                        }
                        // if the audioFile is uploaded on the database
                        else if (cloudStorageUploadStatus == 2)
                        {
                            // Delete the file from CloudStorage if Internet Connection is Available

                            String[] splittedString = audioFileLocationinAudioDB.Split('\\');

                            String fileLocationInDropBoxLocalFolder = dropBoxFolderLocation + splittedString[splittedString.Length - 1];
                            bool isDeleted = objectFileFunctions.deleteFile(fileLocationInDropBoxLocalFolder);

                            // it might be possible that a file is deleted from cloud storage
                            // but the DB function to delete the file from audioDB fails
                            // in the next iteration the file would already be deleted and previous
                            //function would always return false and hence the file would never be deleted
                            // from the audio DB..Here is the solution to fix that problem
                            if (isDeleted == false)
                            {
                                // check whether file exists or not on cloud
                                // in case of a dropbox folder just check whether the file physically exists

                                bool isFileExists = System.IO.File.Exists(fileLocationInDropBoxLocalFolder);

                                // if file does not exists then its already deleted from the dropbox folder
                                if (!isFileExists)
                                {
                                    // set the isDeleted flag to true to delete the files from audio DB
                                    isDeleted = true;
                                }
                            }


                            // If the delete operation is success, then delete the file from audio Database
                            if (isDeleted)
                            {
                                EventLog.WriteEntry(sSource, "As Youtube video is absent, file is deleted from Dropbox", EventLogEntryType.Error);

                                // Delete the audio File from the Database
                                bool isDeletionSuccess = objectDatabaseFunctions.deleteAudioFileFromDatabase(audioFileLocationinAudioDB);

                                // Also delete the file from hard disk, if file is successfully deleted from the audio DB
                                if (isDeletionSuccess)
                                {
                                    objectFileFunctions.deleteFile(audioFileLocationinAudioDB);
                                    EventLog.WriteEntry(sSource, "As Youtube video is absent, file is deleted from DropBox, audio DB and harddisk", EventLogEntryType.Error);

                                }
                            }
                            
                        }
                        else
                        {
                            // Delete the audio from the database
                            bool isDeleted = objectDatabaseFunctions.deleteAudioFileFromDatabase(audioFileLocationinAudioDB);
                            // also delete the file from harddisk
                            if (isDeleted)
                            {
                                objectFileFunctions.deleteFile(audioFileLocationinAudioDB);
                                EventLog.WriteEntry(sSource, "As YT video is absent, Audio file deleted from the database and from hard disk", EventLogEntryType.Information);

                            }
                        }

                    }

                    // else if video is present on Youtube check its status
                    else
                    {
                        Tuple<Boolean, String, Boolean> youTubeModerationStatusandVideoStatus =
                            (Tuple<Boolean, String, Boolean>)youTubeVideoRecord[youTubeVideoIDinAudioDB];
                        Boolean isPrivateOnYouTube = youTubeModerationStatusandVideoStatus.Item1;
                        String youTubeVideoStatus = youTubeModerationStatusandVideoStatus.Item2;
                        Boolean hasToUploadOnFacebook = youTubeModerationStatusandVideoStatus.Item3;

                        if (youTubeVideoStatus == "rejected" || youTubeVideoStatus == "failed")
                        {
                            
                            EventLog.WriteEntry(sSource, "Status of Youtube video is rejected or failed", EventLogEntryType.Information);

                            // delete the video from Youtube

                            if (internetConnectionAvailable())
                            {
                                bool isDeleted = objectYouTubeFunctions.deleteVideoFromYouTube(youTubeVideoIDinAudioDB);


                                if (isDeleted)
                                {
                                    // delete the corresponding audio from the database
                                    bool fileDeletedFromDB = objectDatabaseFunctions.deleteAudioFileFromDatabase(audioFileLocationinAudioDB);
                                    EventLog.WriteEntry(sSource, "Rejected or failed video deleted from YouTube and corresponding file from Audio DB", EventLogEntryType.Information);

                                    // also delete the file from the harddisk
                                    if (fileDeletedFromDB)
                                    {
                                        objectFileFunctions.deleteFile(audioFileLocationinAudioDB);
                                        EventLog.WriteEntry(sSource, "audio file corresponding to rejected/failed video is deleted from harddisk", EventLogEntryType.Information);

                                    }
                                }
                            }

                            // no need to check anything from skydrive or dropbox as a moderator cannot moderate
                            // a failed or rejected video on YouTube. Hence the audio will never be uploaded 
                            // on skydrive in our business logic.
                            // FIXED A BUG HERE, EARLIER COUNT was Not Incremented
                            count++;
                            // use continue keyword as we do not need to process the audio/video futher
                            continue;
                        }

                        // if the moderation status for audio in DB and video on YouTube is same then 
                        // we don't need to do anything. 

                        // if the content is made public at YouTube and is Private at audio database
                        if (isPrivateOnYouTube == false && isPrivateInAudioDB == true)
                        {
                            // update the moderation status and mark the audio to be uploaded on cloud storage if its cloudstoragestatus is 0
                            // also set the cloudstoragestatus to 1 

                            bool isUpdateSuccess = objectDatabaseFunctions.updateModerationStatusCloudStorageStatusFBUploadStatusofAudio(audioFileLocationinAudioDB, false, hasToUploadOnFacebook);

                            // log this event in the event logger 
                            EventLog.WriteEntry(sSource, "Moderation Status and CloudStorageUploadStatus are modified? : " + isUpdateSuccess, EventLogEntryType.Information);
                        }
                        // if the content is made private on YouTube but its public in the audio database
                        else if (isPrivateOnYouTube == true && isPrivateInAudioDB == false)
                        {
                            // update the moderation status and unmark the audio to be uploaded on cloud storage if its cloudstoragestatus is 1
                            // also set the cloudstoragestatus to 0
                            bool isUpdateSuccess = objectDatabaseFunctions.updateModerationStatusCloudStorageStatusFBUploadStatusofAudio(audioFileLocationinAudioDB, true, hasToUploadOnFacebook);

                            // log this event in the event logger 
                            EventLog.WriteEntry(sSource, "Moderation Status and CloudStorageUploadStatus are modified? : " + isUpdateSuccess, EventLogEntryType.Information);
                            
                        }
                        else if (isPrivateOnYouTube == false && isPrivateInAudioDB == false)
                        {
                            // if a video has to be uploaded on the facebook
                            if (hasToUploadOnFacebook)
                            {
                                int facebookUploadStatus = objectDatabaseFunctions.getFacebookUploadStatusOfAudioFile(audioFileLocationinAudioDB);

                                if (facebookUploadStatus == 0)
                                    objectDatabaseFunctions.updateFacebookUploadStatus(youTubeVideoIDinAudioDB, 1);
                            }
                            else
                            {
                                int facebookUploadStatus = objectDatabaseFunctions.getFacebookUploadStatusOfAudioFile(audioFileLocationinAudioDB);

                                if (facebookUploadStatus == 1)
                                    objectDatabaseFunctions.updateFacebookUploadStatus(youTubeVideoIDinAudioDB, 0);
                            }
                        }

                    }


                    count++;

                } // inner while loop ends, all the table is proccessed


                // Now get all the audio Files from the database corresponding to the CloudStorageUploadStatus = 1
                // Upload them on SkyDrive by just putting them in the DropBox Folder

                // Upload the files to a cloud storage only if the isSynchronizeBranch is set to true
                if (isSynchronizeBranch)
                {
                    EventLog.WriteEntry(sSource, "Code to upload audio files in dropbox folder starts executing", EventLogEntryType.Information);
                    List<String> audioFileLocationsToBeUploadedOnDropBoxLocalFolder =
                        objectDatabaseFunctions.getAudioFilesToBeUploadedOnCloudStorage();

                    if (audioFileLocationsToBeUploadedOnDropBoxLocalFolder == null)
                    {
                        // log this information in the event logger
                        EventLog.WriteEntry(sSource, "Error in fetching audio files location to be uploaded on DropBox", EventLogEntryType.Error);
                        
                    }
                    else
                    {
                        int flag = 0;
                        EventLog.WriteEntry(sSource, "Audio files to be uploaded on Dropbox is successfully fetched", EventLogEntryType.Information);

                        while (flag < audioFileLocationsToBeUploadedOnDropBoxLocalFolder.Count)
                        {
                            EventLog.WriteEntry(sSource, "Uploading the audio file on DropBox", EventLogEntryType.Information);

                            // copy the audio file in the DropBox Synchronized folder 
                            bool isCopySuccess =
                                objectFileFunctions.copyFile(audioFileLocationsToBeUploadedOnDropBoxLocalFolder[flag], dropBoxFolderLocation);

                            // if upload is successful then 
                            //change the CloudStorageUploadStatus to 2 in the audio DB corresponding to the file
                            if (isCopySuccess)
                            {
                                objectDatabaseFunctions.updateCloudStorageStatus(audioFileLocationsToBeUploadedOnDropBoxLocalFolder[flag], 2);
                                EventLog.WriteEntry(sSource, "Audio file successfully uploaded on Dropbox", EventLogEntryType.Information);

                            }

                            flag++;
                        }  // while loop ends, implies processing on all files is done

                    } // else ends

                }
                // Now get all the audio Files from the database corresponding to the FacebookUploadStatus = 1
                // Upload them to facebook only if the isConfigureFacebook is set to true
                if (isConfigureFacebook)
                {
                    EventLog.WriteEntry(sSource, "Code segment to upload audio files on Facebook starts executing", EventLogEntryType.Information);

                    List<String> YouTubeVideoIdOfAudioFilesToBeUploadedOnFacebook =
                        objectDatabaseFunctions.getYouTubeVideoIdOfAudioFilesToBeUploadedOnFacebook();

                    if (YouTubeVideoIdOfAudioFilesToBeUploadedOnFacebook == null)
                    {
                        // log this information in the event logger
                        EventLog.WriteEntry(sSource, "Unable to fetch the data to be uploaded on Facebook", EventLogEntryType.Error);

                    }
                    else
                    {
                        EventLog.WriteEntry(sSource, "Data to be uploaded on Facebook is successfully fetched", EventLogEntryType.Information);
                        
                        int flag = 0;
                        FacebookFunctions objectFacebookFunctions = new FacebookFunctions();
                        while (flag < YouTubeVideoIdOfAudioFilesToBeUploadedOnFacebook.Count)
                        {
                            String youTubeVideoId = YouTubeVideoIdOfAudioFilesToBeUploadedOnFacebook[flag];
                            // get the video information spec 
                            var videoInformation = objectYouTubeFunctions.getVideoInformation(youTubeVideoId);
                            // attempt to push data on facebook
                            bool isUploadSuccess = objectFacebookFunctions.uploadLink(videoInformation.Item1, videoInformation.Item2,
                                videoInformation.Item3.ToString(),facebookAccessToken);

                            // if upload is successful then 
                            //change the CloudStorageUploadStatus to 2 in the audio DB corresponding to the file
                            if (isUploadSuccess)
                            {
                                objectDatabaseFunctions.updateFacebookUploadStatus(youTubeVideoId, 2);
                                EventLog.WriteEntry(sSource, "Audio file data successfully uploaded on Facebook", EventLogEntryType.Information);

                            }

                            flag++;
                        }  // while loop ends, implies processing on all files is done

                    } // else ends
                }
                

                // Sleep for 1 minute
                EventLog.WriteEntry(sSource, "Moderation Service has finished executing tasks and now is going to sleep for some time", EventLogEntryType.Information);
                Thread.Sleep(60000);

            } // infinite while loop ends here

        } // ServiceLogicFunction ends here

        public bool internetConnectionAvailable()
        {
            System.Uri Url = new System.Uri("http://www.google.com");

            // by default returning true
            bool internetConnectionAvailableFlag = true;

            System.Net.WebRequest objectWebRequest = null;
            System.Net.WebResponse objectWebResponse;

            try
            {
                objectWebRequest = System.Net.WebRequest.Create(Url);
            }
            catch (System.NotSupportedException notSupportedException)
            {
                // log exceptions
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + notSupportedException.Message, EventLogEntryType.Error);
            }
            catch (System.ArgumentNullException argumentNullException)
            {
                // log exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + argumentNullException.Message, EventLogEntryType.Error);
            }
            catch (System.Security.SecurityException securityException)
            {
                // log exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + securityException.Message, EventLogEntryType.Error);
             }

            try
            {
                objectWebResponse = objectWebRequest.GetResponse();
                objectWebResponse.Close();
                internetConnectionAvailableFlag = true;
                EventLog.WriteEntry(sSource, "Internet connection is available", EventLogEntryType.Information);
            }

            catch (System.NotImplementedException notImplementedException)
            {
                // log the exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + notImplementedException.Message, EventLogEntryType.Error);
                internetConnectionAvailableFlag = false;
            }
            catch (Exception generalException)
            {
                // log the exception
                EventLog.WriteEntry(sSource, "Internet Connection Available:" + generalException.Message, EventLogEntryType.Error);
                internetConnectionAvailableFlag = false;
            }
            finally
            {
                objectWebRequest = null;
            }

            return internetConnectionAvailableFlag;
        }

    }
}
